/**
 *  BytecodeParser.java : IceBird project
 *  7:07:38 
 */
package icebird.compiler.bytecode;

import icebird.metadata.constpool.*;

import java.nio.ByteBuffer;

/**
 * 
 * @author Sergey Shulepoff[Knott]
 */
public class BytecodeParser {

	private ByteBuffer bytecode;

	private BytecodeVisitor handler;

	private ByteCode bc;

	/**
	 * Construct bytecode parser.
	 * 
	 * @param bytecode
	 * @param handler
	 */
	public BytecodeParser(ByteCode bc, BytecodeVisitor handler) {
		this.bytecode = bc.getBytes();
		
		this.handler = handler;
		this.bc = bc;
	}

	/**
	 * Parse all bytecodes.
	 * 
	 * @return void
	 */
	public void parse() {
		parse(0, bytecode.limit(), true);
	}

	/**
	 * Parse bytecodes in range.
	 * 
	 * @return void
	 */
	public void parse(int start, int end, boolean startEnd) {
		bytecode.position(start);

		if (startEnd) {
			if (!handler.startMethod(bc.getMethod())) return;
		}

		ConstPool pool = bc.getConstPool();

		while (bytecode.position() < end) {
			int opcode = getu1();

			int address = bytecode.position() - 1;
			handler.startInstruction(address, (byte) opcode);

			switch (opcode) {

			case 0x00:
				handler.visit_nop();
				break;
			case 0x01:
				handler.visit_aconst_null();
				break;
			case 0x02:
				handler.visit_iconst(-1);
				break;
			case 0x03:
				handler.visit_iconst(0);
				break;
			case 0x04:
				handler.visit_iconst(1);
				break;
			case 0x05:
				handler.visit_iconst(2);
				break;
			case 0x06:
				handler.visit_iconst(3);
				break;
			case 0x07:
				handler.visit_iconst(4);
				break;
			case 0x08:
				handler.visit_iconst(5);
				break;
			case 0x09:
				handler.visit_lconst(0L);
				break;

			case 0x0a:
				handler.visit_lconst(1l);
				break;
			case 0x0b:
				handler.visit_fconst(0.0f);
				break;
			case 0x0c:
				handler.visit_fconst(1.0f);
				break;
			case 0x0d:
				handler.visit_fconst(2.0f);
				break;
			case 0x0e:
				handler.visit_dconst(0.0);
				break;
			case 0x0f:
				handler.visit_dconst(1.0);
				break;
			case 0x10:
				handler.visit_iconst(gets1()); // bipush
				break;
			case 0x11:
				handler.visit_iconst(gets2()); // sipush
				break;

			case 0x12:
			case 0x13: {
				int index = 0;
				if (opcode == 0x12) {
					index = getu1();
				} else {
					index = getu2();
				}

				int type = pool.getType(index);
				switch (type) {
				case ConstObject.CONST_INTEGER:
					handler.visit_iconst(pool.getInteger(index));
					break;

				case ConstObject.CONST_FLOAT:
					handler.visit_fconst(pool.getFloat(index));
					break;

				case ConstObject.CONST_STRING:
					handler.visit_ldc(pool.getString(index));
					break;

				default:
					throw new RuntimeException(
							"Unknown(Unexcepted) constant type");
				}
				break;
			}

			case 0x14: {
				int index = getu2();
				int type = pool.getType(index);

				switch (type) {
				case ConstObject.CONST_DOUBLE:
					handler.visit_dconst(pool.getDouble(index));
					break;

				case ConstObject.CONST_LONG:
					handler.visit_lconst(pool.getLong(index));
					break;

				default:
					throw new RuntimeException(
							"Unknown(Unexcepted) constant type");
				}
				break;
			}

			case 0x15:
				handler.visit_iload(getu1());
				break;
			case 0x16:
				handler.visit_lload(getu1());
				break;
			case 0x17:
				handler.visit_fload(getu1());
				break;
			case 0x18:
				handler.visit_dload(getu1());
				break;
			case 0x19:
				handler.visit_aload(getu1());
				break;
			case 0x1a:
				handler.visit_iload(0);
				break;
			case 0x1b:
				handler.visit_iload(1);
				break;
			case 0x1c:
				handler.visit_iload(2);
				break;
			case 0x1d:
				handler.visit_iload(3);
				break;
			// -- 30 --
			case 0x1e:
				handler.visit_lload(0);
				break;
			case 0x1f:
				handler.visit_lload(1);
				break;
			case 0x20:
				handler.visit_lload(2);
				break;
			case 0x21:
				handler.visit_lload(3);
				break;
			case 0x22:
				handler.visit_fload(0);
				break;
			case 0x23:
				handler.visit_fload(1);
				break;
			case 0x24:
				handler.visit_fload(2);
				break;
			case 0x25:
				handler.visit_fload(3);
				break;
			case 0x26:
				handler.visit_dload(0);
				break;
			case 0x27:
				handler.visit_dload(1);
				break;
			// -- 40 --
			case 0x28:
				handler.visit_dload(2);
				break;
			case 0x29:
				handler.visit_dload(3);
				break;
			case 0x2a:
				handler.visit_aload(0);
				break;
			case 0x2b:
				handler.visit_aload(1);
				break;
			case 0x2c:
				handler.visit_aload(2);
				break;
			case 0x2d:
				handler.visit_aload(3);
				break;
			case 0x2e:
				handler.visit_iaload();
				break;
			case 0x2f:
				handler.visit_laload();
				break;
			case 0x30:
				handler.visit_faload();
				break;
			case 0x31:
				handler.visit_daload();
				break;
			// -- 50 --
			case 0x32:
				handler.visit_aaload();
				break;
			case 0x33:
				handler.visit_baload();
				break;
			case 0x34:
				handler.visit_caload();
				break;
			case 0x35:
				handler.visit_saload();
				break;
			case 0x36:
				handler.visit_istore(getu1());
				break;
			case 0x37:
				handler.visit_lstore(getu1());
				break;
			case 0x38:
				handler.visit_fstore(getu1());
				break;
			case 0x39:
				handler.visit_dstore(getu1());
				break;
			case 0x3a:
				handler.visit_astore(getu1());
				break;
			case 0x3b:
				handler.visit_istore(0);
				break;
			// -- 60 --
			case 0x3c:
				handler.visit_istore(1);
				break;
			case 0x3d:
				handler.visit_istore(2);
				break;
			case 0x3e:
				handler.visit_istore(3);
				break;
			case 0x3f:
				handler.visit_lstore(0);
				break;
			case 0x40:
				handler.visit_lstore(1);
				break;
			case 0x41:
				handler.visit_lstore(2);
				break;
			case 0x42:
				handler.visit_lstore(3);
				break;
			case 0x43:
				handler.visit_fstore(0);
				break;
			case 0x44:
				handler.visit_fstore(1);
				break;
			case 0x45:
				handler.visit_fstore(2);
				break;
			// -- 70 --
			case 0x46:
				handler.visit_fstore(3);
				break;
			case 0x47:
				handler.visit_dstore(0);
				break;
			case 0x48:
				handler.visit_dstore(1);
				break;
			case 0x49:
				handler.visit_dstore(2);
				break;
			case 0x4a:
				handler.visit_dstore(3);
				break;
			case 0x4b:
				handler.visit_astore(0);
				break;
			case 0x4c:
				handler.visit_astore(1);
				break;
			case 0x4d:
				handler.visit_astore(2);
				break;
			case 0x4e:
				handler.visit_astore(3);
				break;
			case 0x4f:
				handler.visit_iastore();
				break;
			// -- 80 --
			case 0x50:
				handler.visit_lastore();
				break;
			case 0x51:
				handler.visit_fastore();
				break;
			case 0x52:
				handler.visit_dastore();
				break;
			case 0x53:
				handler.visit_aastore();
				break;
			case 0x54:
				handler.visit_bastore();
				break;
			case 0x55:
				handler.visit_castore();
				break;
			case 0x56:
				handler.visit_sastore();
				break;
			case 0x57:
				handler.visit_pop();
				break;
			case 0x58:
				handler.visit_pop2();
				break;
			case 0x59:
				handler.visit_dup();
				break;
			// -- 90 --
			case 0x5a:
				handler.visit_dup_x1();
				break;
			case 0x5b:
				handler.visit_dup_x2();
				break;
			case 0x5c:
				handler.visit_dup2();
				break;
			case 0x5d:
				handler.visit_dup2_x1();
				break;
			case 0x5e:
				handler.visit_dup2_x2();
				break;
			case 0x5f:
				handler.visit_swap();
				break;
			case 0x60:
				handler.visit_iadd();
				break;
			case 0x61:
				handler.visit_ladd();
				break;
			case 0x62:
				handler.visit_fadd();
				break;
			case 0x63:
				handler.visit_dadd();
				break;
			// -- 100 --
			case 0x64:
				handler.visit_isub();
				break;
			case 0x65:
				handler.visit_lsub();
				break;
			case 0x66:
				handler.visit_fsub();
				break;
			case 0x67:
				handler.visit_dsub();
				break;
			case 0x68:
				handler.visit_imul();
				break;
			case 0x69:
				handler.visit_lmul();
				break;
			case 0x6a:
				handler.visit_fmul();
				break;
			case 0x6b:
				handler.visit_dmul();
				break;
			case 0x6c:
				handler.visit_idiv();
				break;
			case 0x6d:
				handler.visit_ldiv();
				break;
			// -- 110 --
			case 0x6e:
				handler.visit_fdiv();
				break;
			case 0x6f:
				handler.visit_ddiv();
				break;
			case 0x70:
				handler.visit_irem();
				break;
			case 0x71:
				handler.visit_lrem();
				break;
			case 0x72:
				handler.visit_frem();
				break;
			case 0x73:
				handler.visit_drem();
				break;
			case 0x74:
				handler.visit_ineg();
				break;
			case 0x75:
				handler.visit_lneg();
				break;
			case 0x76:
				handler.visit_fneg();
				break;
			case 0x77:
				handler.visit_dneg();
				break;
			// -- 120 --
			case 0x78:
				handler.visit_ishl();
				break;
			case 0x79:
				handler.visit_lshl();
				break;
			case 0x7a:
				handler.visit_ishr();
				break;
			case 0x7b:
				handler.visit_lshr();
				break;
			case 0x7c:
				handler.visit_iushr();
				break;
			case 0x7d:
				handler.visit_lushr();
				break;
			case 0x7e:
				handler.visit_iand();
				break;
			case 0x7f:
				handler.visit_land();
				break;
			case 0x80:
				handler.visit_ior();
				break;
			case 0x81:
				handler.visit_lor();
				break;
			// -- 130 --
			case 0x82:
				handler.visit_ixor();
				break;
			case 0x83:
				handler.visit_lxor();
				break;
			case 0x84:
				handler.visit_iinc(getu1(), gets1());
				break;

			case 0x85:
				handler.visit_i2l();
				break;
			case 0x86:
				handler.visit_i2f();
				break;
			case 0x87:
				handler.visit_i2d();
				break;
			case 0x88:
				handler.visit_l2i();
				break;
			case 0x89:
				handler.visit_l2f();
				break;
			case 0x8a:
				handler.visit_l2d();
				break;
			case 0x8b:
				handler.visit_f2i();
				break;
			// -- 140 --
			case 0x8c:
				handler.visit_f2l();
				break;
			case 0x8d:
				handler.visit_f2d();
				break;
			case 0x8e:
				handler.visit_d2i();
				break;
			case 0x8f:
				handler.visit_d2l();
				break;
			case 0x90:
				handler.visit_d2f();
				break;
			case 0x91:
				handler.visit_i2b();
				break;
			case 0x92:
				handler.visit_i2c();
				break;
			case 0x93:
				handler.visit_i2s();
				break;
			case 0x94:
				handler.visit_lcmp();
				break;
			case 0x95:
				handler.visit_fcmpl();
				break;
			// -- 150 --
			case 0x96:
				handler.visit_fcmpg();
				break;
			case 0x97:
				handler.visit_dcmpl();
				break;
			case 0x98:
				handler.visit_dcmpg();
				break;
			case 0x99:
				handler.visit_ifeq(address + gets2());
				break;
			case 0x9a:
				handler.visit_ifne(address + gets2());
				break;
			case 0x9b:
				handler.visit_iflt(address + gets2());
				break;
			case 0x9c:
				handler.visit_ifge(address + gets2());
				break;
			case 0x9d:
				handler.visit_ifgt(address + gets2());
				break;
			case 0x9e:
				handler.visit_ifle(address + gets2());
				break;
			case 0x9f:
				handler.visit_if_icmpeq(address + gets2());
				break;
			// -- 160 --
			case 0xa0:
				handler.visit_if_icmpne(address + gets2());
				break;
			case 0xa1:
				handler.visit_if_icmplt(address + gets2());
				break;
			case 0xa2:
				handler.visit_if_icmpge(address + gets2());
				break;
			case 0xa3:
				handler.visit_if_icmpgt(address + gets2());
				break;
			case 0xa4:
				handler.visit_if_icmple(address + gets2());
				break;
			case 0xa5:
				handler.visit_if_acmpeq(address + gets2());
				break;
			case 0xa6:
				handler.visit_if_acmpne(address + gets2());
				break;
			case 0xa7:
				handler.visit_goto(address + gets2());
				break;
			case 0xa8:
				handler.visit_jsr(address + gets2(), address + 2);
				break;
			case 0xa9:
				handler.visit_ret(getu1());
				break;
			// -- 170 --
			case 0xaa: {
				skipPadding();
				int defAddress = address + gets4();
				int lowValue = gets4();
				int highValue = gets4();
				if (highValue < lowValue) {
					throw new RuntimeException("tableSwitch high < low! (high="
							+ highValue + ", low=" + lowValue + ")");
				}
				int cnt = highValue - lowValue + 1;
				int addresses[] = new int[cnt];
				for (int i = 0; i < cnt; i++) {
					addresses[i] = address + gets4();
				}
				handler.visit_tableswitch(defAddress, lowValue, highValue,
						addresses);
			}
				break;
			case 0xab: {
				skipPadding();
				int defAddress = address + gets4();
				int cnt = getu4();
				int matches[] = new int[cnt];
				int addresses[] = new int[cnt];
				for (int i = 0; i < cnt; i++) {
					matches[i] = gets4();
					addresses[i] = address + gets4();
				}
				handler.visit_lookupswitch(defAddress, matches, addresses);
			}
				break;
			case 0xac:
				handler.visit_ireturn();
				break;
			case 0xad:
				handler.visit_lreturn();
				break;
			case 0xae:
				handler.visit_freturn();
				break;
			case 0xaf:
				handler.visit_dreturn();
				break;
			case 0xb0:
				handler.visit_areturn();
				break;
			case 0xb1:
				handler.visit_return();
				break;
			case 0xb2: {
				ConstField field = pool.getField(getu2());
				handler.visit_getstatic(field);
			}
				break;
			case 0xb3: {
				ConstField field = pool.getField(getu2());
				handler.visit_putstatic(field);
			}
				break;
			// -- 180 --
			case 0xb4: {
				ConstField field = pool.getField(getu2());
				handler.visit_getfield(field);
			}
				break;
			case 0xb5: {
				ConstField field = pool.getField(getu2());
				handler.visit_putfield(field);
			}
				break;

			case 0xb6:
				handler.visit_invokevirtual(pool.getMethod(getu2()));
				break;

			case 0xb7:
				handler.visit_invokespecial(pool.getMethod(getu2()));
				break;

			case 0xb8:
				handler.visit_invokestatic(pool.getMethod(getu2()));
				break;
			case 0xb9: {
				ConstIMethod ref = pool.getIMethod(getu2());
				int count = getu1();
				skip();
				handler.visit_invokeinterface(ref, count);
			}
				break;

			case 0xbb:
				handler.visit_new(pool.getConstClass(getu2()));
				break;

			case 0xbc:
				handler.visit_newarray(getu1());
				break;
			case 0xbd:
				handler.visit_anewarray(pool.getConstClass(getu2()));
				break;
			// -- 190 --
			case 0xbe:
				handler.visit_arraylength();
				break;
			case 0xbf:
				handler.visit_athrow();
				break;
			case 0xc0:
				handler.visit_checkcast(pool.getConstClass(getu2()));
				break;
			case 0xc1:
				handler.visit_instanceof(pool.getConstClass(getu2()));
				break;
			case 0xc2:
				handler.visit_monitorenter();
				break;
			case 0xc3:
				handler.visit_monitorexit();
				break;
			case 0xc5: {
				ConstClass clazz = pool.getConstClass(getu2());
				int dims = getu1();
				handler.visit_multianewarray(clazz, dims);
			}
				break;
			case 0xc6:
				handler.visit_ifnull(address + gets2());
				break;
			case 0xc7:
				handler.visit_ifnonnull(address + gets2());
				break;
			// -- 200 --
			case 0xc8:
				handler.visit_goto(address + gets4());
				break;
			case 0xc9:
				handler.visit_jsr(address + gets4(), address + 4);
				break;
				
            case 0xc4:
            {
                int opcode2 = getu1();
                if (opcode2 == 0x84) {
                    int idx = getu2();
                    int constValue = gets2();
                    handler.visit_iinc(idx, constValue);
                } else {
                    int idx = getu2();
                    switch (opcode2) {
                    case 0x15:
                        handler.visit_iload(idx);
                        break;
                    case 0x16:
                        handler.visit_lload(idx);
                        break;
                    case 0x17:
                        handler.visit_fload(idx);
                        break;
                    case 0x18:
                        handler.visit_dload(idx);
                        break;
                    case 0x19:
                        handler.visit_aload(idx);
                        break;
                    case 0x36:
                        handler.visit_istore(idx);
                        break;
                    case 0x37:
                        handler.visit_lstore(idx);
                        break;
                    case 0x38:
                        handler.visit_fstore(idx);
                        break;
                    case 0x39:
                        handler.visit_dstore(idx);
                        break;
                    case 0x3a:
                        handler.visit_astore(idx);
                        break;
                    default:
                        throw new RuntimeException(
                                "Invalid opcode in wide instruction");
                    }
                }
            }
            break;
				
			default:
				throw new RuntimeException("Invalid opcode");
			}
		}
		
		if (startEnd) {
			handler.endMethod();
		}
	}

	private final void skipPadding() {
		while (bytecode.position() % 4 != 0) {
			skip();
		}
	}

	private final void skip() {
		bytecode.get();
	}

	/**
	 * Get an unsigned byte from the next bytecode position
	 * 
	 * @return int
	 */
	private final int getu1() {
		return bytecode.get() & 0xFF;
	}

	/**
	 * Get an unsigned short from the next bytecode position
	 * 
	 * @return int
	 */
	private final int getu2() {
		int v1 = bytecode.get() & 0xFF;
		int v2 = bytecode.get() & 0xFF;
		return (v1 << 8) | v2;
	}

	/**
	 * Get an unsigned int from the next bytecode position
	 * 
	 * @return int
	 */
	private final int getu4() {
		int v1 = bytecode.get() & 0xFF;
		int v2 = bytecode.get() & 0xFF;
		int v3 = bytecode.get() & 0xFF;
		int v4 = bytecode.get() & 0xFF;
		return (v1 << 24) | (v2 << 16) | (v3 << 8) | v4;
	}

	/**
	 * Get a byte from the next bytecode position
	 * 
	 * @return byte
	 */
	private final byte gets1() {
		return bytecode.get();
	}

	/**
	 * Get a short from the next bytecode positions
	 * 
	 * @return short
	 */
	private final short gets2() {
		int v1 = bytecode.get() & 0xFF;
		int v2 = bytecode.get() & 0xFF;
		return (short) ((v1 << 8) | v2);
	}

	/**
	 * Get an int from the next bytecode position
	 * 
	 * @return int
	 */
	private final int gets4() {
		int v1 = bytecode.get() & 0xFF;
		int v2 = bytecode.get() & 0xFF;
		int v3 = bytecode.get() & 0xFF;
		int v4 = bytecode.get() & 0xFF;
		return (v1 << 24) | (v2 << 16) | (v3 << 8) | v4;
	}
}
